﻿using System.IO;
using System.Linq;
using System.Text;
using System.Web;
using System.Web.Routing;

namespace CodeContrib.Web.Routing
{
	public class RouteDebuggerHttpHandler : IHttpHandler
	{
		public bool IsReusable
		{
			get { return true; }
		}

		public RequestContext RequestContext { get; set; }

		private static string FormatRouteValues(RouteValueDictionary routeValues)
		{
			if (routeValues == null
			    || !routeValues.Any())
			{
				return "(null)";
			}

			var display = new StringBuilder();

			for (var i = 0; i < routeValues.Count; i++)
			{
				var keyValuePair = routeValues.ElementAt(i);

				display.Append(string.Format("{0} = {1}", keyValuePair.Key, keyValuePair.Value));

				if (i < routeValues.Count - 1)
				{
					display.Append(", ");
				}
			}

			return display.ToString();
		}

		public void ProcessRequest(HttpContext context)
		{
			const string htmlFormat = @"
<!DOCTYPE html>
<html lang=""en"">
<head>
	<link rel=""stylesheet"" href=""http://twitter.github.com/bootstrap/assets/css/bootstrap.css"" />
	<link rel=""stylesheet"" href=""http://twitter.github.com/bootstrap/assets/css/bootstrap-responsive.css"" />
	<style type=""text/css"">
		body, p, input, textarea
		{{
			font-family: ""Trebuchet MS"", ""Helvetica Neue"", Helvetica, Arial, sans-serif;
		}}	

		.masthead
		{{
			padding-top: 36px;
			margin-bottom: 36px;
		}}

		.masthead h1
		{{
			margin-bottom: 18px;
			text-align: center;
		}}

		table.shrink
		{{
			width: auto;
		}}

		.true
		{{
			color: green;
		}}

		.false
		{{
			color: red;
		}}
	</style>
	<title>Route Debugger</title>
</head>
<body>
	<div class=""container"">
		<div class=""row"">
			<div class=""span12"">
				<div class=""masthead"">
					<h1>Route Debugger</h1>
					<p class="""">
						Type in a url in the address bar to see which defined routes match it. 
						A {{*catchall}} route is added to the list of routes automatically in 
						case none of your routes match.
					</p>
					<p>
						To generate URLs using routing, supply route values via the query string: <code>http://{0}?controller={{controller}}&action={{action}}</code>
					</p>
				</div>
				<h2>Matched Route</h2>
				{1}
				{2}
				<div class=""row"">
					<div class=""span6"">
						<h3>Route Data</h3>
						<table class=""table table-striped table-bordered table-condensed shrink"">
							<tr>
								<th>
									Key
								</th>
								<th>
									Value
								</th>
							</tr>
							{3}
						</table>
					</div>
					<div class=""span6"">
						<h3>Data Tokens</h3>
						<table class=""table table-striped table-bordered table-condensed shrink"">
							<tr>
								<th>
									Key
								</th>
								<th>
									Value
								</th>
							</tr>
							{4}
						</table>
					</div>
				</div>
				<h3>All Routes</h3>
				<table class=""table table-striped table-bordered table-condensed"">
					<tr>
						<th>Matches Current Request</th>
						<th>Url</th>
						<th>Defaults</th>
						<th>Constraints</th>
						<th>Data Tokens</th>
					</tr>
					{5}
				</table>
				<h3>Current Request Info</h3>
				<p>
					<strong>AppRelativeCurrentExecutionFilePath</strong>: <code>{6}</code>
				</p>
				<p>
					<strong>PhysicalPath</strong>: <code>{7}</code>
				</p>
			</div>
		</div>
	</div>
</body>
</html>";

			var routeDataRows = string.Empty;

			var routeData = RequestContext.RouteData;
			var routeValues = routeData.Values;
			var matchedRouteBase = routeData.Route;

			var routes = string.Empty;

			using (RouteTable.Routes.GetReadLock())
			{
				foreach (var routeBase in RouteTable.Routes)
				{
					var matchesCurrentRequest = (routeBase.GetRouteData(RequestContext.HttpContext) != null);
					var matchText = string.Format(@"<span class=""{0}"">{0}</span>", matchesCurrentRequest);
					var url = "n/a";
					var defaults = "n/a";
					var constraints = "n/a";
					var dataTokens = "n/a";

					var route = routeBase as Route;

					if (route != null)
					{
						url = route.Url;
						defaults = FormatRouteValues(route.Defaults);
						constraints = FormatRouteValues(route.Constraints);
						dataTokens = FormatRouteValues(route.DataTokens);
					}

					routes += string.Format(@"<tr><td>{0}</td><td>{1}</td><td>{2}</td><td>{3}</td><td>{4}</td></tr>", matchText.ToStringLower(), url, defaults, constraints, dataTokens);
				}
			}

			var matchedRouteUrl = "<p>n/a</p>";

			var dataTokensRows = string.Empty;

			if (!(matchedRouteBase is DebugRoute))
			{
				foreach (var key in routeValues.Keys)
				{
					routeDataRows += string.Format("\t<tr><td>{0}</td><td>{1}&nbsp;</td></tr>", key, routeValues[key]);
				}

				foreach (var key in routeData.DataTokens.Keys)
				{
					dataTokensRows += string.Format("\t<tr><td>{0}</td><td>{1}&nbsp;</td></tr>", key, routeData.DataTokens[key]);
				}

				var matchedRoute = matchedRouteBase as Route;

				if (matchedRoute != null)
				{
					matchedRouteUrl = string.Format("<div class=\"alert alert-success\">{0}</div>", matchedRoute.Url.AsNullIfEmpty() ?? "&nbsp;");
				}
			}
			else
			{
				matchedRouteUrl = "<div class=\"alert alert-error\">NO MATCH!</div>";
			}

			var generatedUrlInfo = string.Empty;

			if (context.Request.QueryString.Count > 0)
			{
				var queryString = routeValues;

				foreach (var key in context.Request.QueryString.AllKeys)
				{
					if (string.IsNullOrEmpty(key))
					{
						continue;
					}

					foreach (var value in context.Request.QueryString.GetValues(key))
					{
						queryString.Merge(key, value, true);
					}
				}

				var virtualPath = RouteTable.Routes.GetVirtualPath(RequestContext, queryString);

				if (virtualPath != null)
				{
					generatedUrlInfo = "<p>Generated URL: ";
					generatedUrlInfo += "<code>" + virtualPath.VirtualPath + "</code>";

					var route = virtualPath.Route as Route;

					if (route != null)
					{
						generatedUrlInfo += " using the route \"" + route.Url + "\"</p>";
					}
				}
			}

			context.Response.Write(string.Format(htmlFormat, (context.Request.Url.Port == 80) ? context.Request.Url.Host : string.Format("{0}:{1}", context.Request.Url.Host, context.Request.Url.Port), matchedRouteUrl, generatedUrlInfo, routeDataRows, dataTokensRows, routes, context.Request.AppRelativeCurrentExecutionFilePath, File.Exists(context.Request.PhysicalPath) ? context.Request.PhysicalPath : "n/a"));
		}
	}
}